package com.easipass.framework.eirframeworkcoredsl;

import com.easipass.framework.eirframeworkcoredsl.graph.Graph;
import org.springframework.util.Assert;

import java.util.*;

public class AppNode {

    private String name;

    private String appDsl;

    private SourceDestinationNode sourceDestinationNode;

    private SinkDestinationNode sinkDestinationNode;

    private List<LabelledServiceNode> sequences;

    private List<LabelledServiceNode> serviceApps;

    AppNode(String name, String appDsl, List<LabelledServiceNode> sequences,
            SourceDestinationNode sourceDestinationNode,
            SinkDestinationNode sinkDestinationNode) {
        this.name = name;
        this.appDsl = appDsl;
        this.sequences = sequences;
        this.sourceDestinationNode = sourceDestinationNode;
        this.sinkDestinationNode = sinkDestinationNode;
    }

    public String getName() {
        return name;
    }

    public String getAppDsl() {
        return appDsl;
    }

    public SourceDestinationNode getSourceDestinationNode() {
        return sourceDestinationNode;
    }

    public SinkDestinationNode getSinkDestinationNode() {
        return sinkDestinationNode;
    }

    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append(name);
        if (serviceApps != null && !serviceApps.isEmpty()) {
            s.append(" ");
            for (LabelledServiceNode serviceNode : serviceApps) {
                s.append("--").append(serviceNode.getName()).append("=").append(serviceNode.toString());
            }
        }
        return s.toString();
    }

    public List<AppValidationProblem> validate() {
        AppValidatorVisitor validator = new AppValidatorVisitor();
        this.accept(validator);
        return validator.getProblems();
    }

    public void accept(Visitor visitor) {
        Assert.notNull(visitor, "visitor expected to be non-null");
        visitor.startVisit(this.name, this.appDsl);
        if(sourceDestinationNode != null){
            sourceDestinationNode.accept(visitor);
        }
        for (LabelledServiceNode lsn : sequences) {
            if (visitor.preVisitSequence(lsn)) {
                lsn.accept(visitor);
                visitor.postVisitSequence(lsn);
            }
        }
        if(sinkDestinationNode !=null){
            sinkDestinationNode.accept(visitor);
        }
        visitor.endVisit();
    }

    public String stringify(boolean includePositionInfo) {
        StringBuilder s = new StringBuilder();
        s.append(getName());
        s.append(":");
        if (this.sourceDestinationNode != null) {
            s.append(sourceDestinationNode.stringify(includePositionInfo));
        }
        if (getServiceApps() != null) {
            for (int i = 0; i < sequences.size(); i++) {
                LabelledServiceNode t = sequences.get(i);
                if (i > 0) {
                    s.append(" ");
                }
                s.append(t.stringify(includePositionInfo));
            }
        }

        if (this.sinkDestinationNode != null) {
            s.append(sinkDestinationNode.stringify(includePositionInfo));
        }
        return s.toString().trim();
    }

    public String stringify() {
        return stringify(false);
    }

    public List<LabelledServiceNode> getSequences() {
        return sequences;
    }

    public List<LabelledServiceNode> getServiceApps() {
        if (serviceApps == null) {
            serviceApps = new ArrayList<>();
            ServiceAppsCollector collector = new ServiceAppsCollector();
            accept(collector);
            serviceApps.add(new FunctionNode(collector.getServiceNodes()));
        }
        return serviceApps;
    }

    public LabelledServiceNode getStart() {
        if (sequences.size() == 0) {
            return null;
        } else {
            return sequences.get(0);
        }
    }

    public LabelledServiceNode getSequenceWithLabel(String label) {
        for (LabelledServiceNode ctn : sequences) {
            if (ctn.hasLabel() && ctn.getLabelString().equals(label)) {
                return ctn;
            }
            for (LabelledServiceNode flow : ctn.getSeries()) {
                if (flow.hasLabel() && flow.getLabelString().equals(label)) {
                    return flow;
                }
            }
        }
        return null;
    }

    public boolean isComposed() {
        if (sequences.size() != 1) {
            return false;
        }
        boolean isOneApp = (sequences.get(0).getSeries().size() == 1 && sequences.get(0).getSeries().get(0).isFlow()
                && ((FlowNode) sequences.get(0).getSeries().get(0)).getSeriesLength() == 1
                && ((FlowNode) sequences.get(0).getSeries().get(0)).getSeriesElement(0).isServiceApp());
        if (!isOneApp) {
            return true;
        }
        ServiceNode singleNode = (ServiceNode) (((FlowNode) sequences.get(0).getSeries().get(0)).getSeriesElement(0));
        return singleNode.hasTransitions();
    }

    public String toExecutableDSL() {
        ExecutableDSLVisitor v = new ExecutableDSLVisitor();
        accept(v);
        return v.getDSL();
    }

    public Graph toGraph() {
        GraphGeneratorVisitor ggv = new GraphGeneratorVisitor();
        this.accept(ggv);
        return ggv.getGraph();
    }

    static class ExecutableDSLVisitor extends Visitor {

        private final static int START_OF_FLOW = 0;

        private final static int START_OF_SPLIT = 1;

        private final static int START_OF_FUNCTION = 2;

        private final static int IN_FLOW = 3;

        private final static int IN_SPLIT = 4;

        private final static int IN_FUNCTION = 5;


        private StringBuilder dsl = new StringBuilder();

        private String appName;

        private Stack<Integer> state = new Stack<>();

        @Override
        public void startVisit(String appName, String appDSL) {
            this.appName = appName;
        }

        @Override
        public boolean preVisit(FlowNode flow) {
            if (!state.isEmpty() && state.peek() == START_OF_FUNCTION) {
                state.pop();
                state.push(IN_FUNCTION);
            } else if (!state.isEmpty() && state.peek() == IN_FLOW) {
                dsl.append(" || ");
            }
            state.push(START_OF_FLOW);
            return true;
        }

        @Override
        public void postVisit(FlowNode flow) {
            if (!state.isEmpty() && state.peek() == START_OF_SPLIT) {
                state.pop();
                state.push(IN_SPLIT);
            }
        }

        @Override
        public boolean preVisit(FunctionNode function) {
            if (!state.isEmpty() && state.peek() == IN_FUNCTION) {
                dsl.append(" | ");
            }
            dsl.append("{");
            return true;
        }

        @Override
        public void postVisit(FunctionNode function) {
            state.pop();
            if (!state.isEmpty() && state.peek() == START_OF_FUNCTION) {
                state.pop();
                state.push(IN_FUNCTION);
            }
            dsl.append("}");
        }

        @Override
        public boolean preVisit(SplitNode split) {
            if (state.peek() == START_OF_FUNCTION) {
                state.pop();
                state.push(IN_FUNCTION);
            } else if (state.peek() == START_OF_FLOW) {
                state.pop();
                state.push(IN_FLOW);
            } else if (state.peek() == IN_FLOW) {
                dsl.append(" && ");
            }
            state.push(START_OF_SPLIT);
            dsl.append("<");
            return true;
        }

        @Override
        public void postVisit(SplitNode split) {
            dsl.append(">");
            state.pop();
        }

        @Override
        public void visit(ServiceNode serviceNode) {
            int currentState = state.peek();
            if (currentState == START_OF_FUNCTION) {
                state.pop();
                state.push(IN_FUNCTION);
            } else if (currentState == START_OF_FLOW) {
                state.pop();
                state.push(IN_FLOW);
            } else if (currentState == IN_FLOW) {
                dsl.append(" && ");
            } else if (currentState == IN_FUNCTION) {
                dsl.append(" | ");
            } else {
                throw new IllegalStateException("" + state.peek());
            }
            dsl.append(toExecutableDSLServiceName(serviceNode));

            List<TransitionNode> transitions = serviceNode.getTransitions();
            for (TransitionNode transition : transitions) {
                dsl.append(" ");
                dsl.append(transition.getStatusToCheckInDSLForm());
                dsl.append("->");
                if (transition.isTargetService()) {
                    dsl.append(toExecutableDSLServiceName(transition.getTargetService()));
                } else {
                    dsl.append(":").append(transition.getTargetLabel());
                }
            }
        }

        private String toExecutableDSLServiceName(ServiceNode serviceNode) {
            StringBuilder taskDefName = new StringBuilder();
            taskDefName.append(appName).append("-");
            if (serviceNode.hasLabel()) {
                taskDefName.append(serviceNode.getLabelString());
            } else {
                taskDefName.append(serviceNode.getName());
            }
            return taskDefName.toString();
        }

        public String getDSL() {
            return dsl.toString();
        }

    }

    class ServiceAppsCollector extends Visitor {

        private String serviceName;

        private List<LabelledServiceNode> serviceNodes = new ArrayList<>();

        @Override
        public void startVisit(String name, String dsl) {
            this.serviceName = name;
        }

//        @Override
//        public void visit(FlowNode flowNode) {
//            if(flowNode.isFlow()){
//                FlowNode flowNode1 = new FlowNode(flowNode.getSeries());
//                serviceNodes.add(flowNode1);
//            }
//        }

        @Override
        public void visit(ServiceNode serviceNode) {
            ServiceNode targetService = new ServiceNode(serviceNode.getToken(), name, serviceNode.getArguments(), serviceNode.getTransitions());
            targetService.setLabel(serviceNode.getLabel());
            serviceNodes.add(targetService);
        }

        @Override
        public void visit(TransitionNode transition) {
            if (transition.isTargetService()) {
                ServiceNode targetService = transition.getTargetService();
                ServiceNode serviceNode = new ServiceNode(targetService.getToken(), name, targetService.getArguments(), targetService.getTransitions());
                serviceNode.setLabel(targetService.getLabel());
                serviceNodes.add(serviceNode);
            }
        }

        public List<LabelledServiceNode> getServiceNodes() {
            return serviceNodes;
        }

    }
}
